﻿using System;
using System.Collections.Generic;
using System.Text;
using ThomasGIS.Geometries;
using ThomasGIS.TrajectoryPackage;

namespace ThomasGIS.Helpers
{
    public static class DistanceCalculator
    {
        public static double SpatialDistance(IPoint p1, IPoint p2)
        {
            double x1 = p1.GetX();
            double x2 = p2.GetX();
            double y1 = p1.GetY();
            double y2 = p2.GetY();
            return Math.Sqrt(Math.Pow((y2 - y1), 2) + Math.Pow((x2 - x1), 2));
        }

        public static double SpatialDistanceGeo(IPoint p1, IPoint p2)
        {
            double lon1 = p1.GetX() / 180.0 * Math.PI;
            double lat1 = p1.GetY() / 180.0 * Math.PI;
            double lon2 = p2.GetX() / 180.0 * Math.PI;
            double lat2 = p2.GetY() / 180.0 * Math.PI;

            double a = lat1 - lat2;
            double b = lon1 - lon2;
            double s = 2 * Math.Asin(Math.Sqrt(Math.Pow(Math.Sin(a / 2), 2) + Math.Cos(lat1) * Math.Cos(lat2) * Math.Pow(Math.Sin(b / 2), 2)));

            s = s * 6378137;
            s = Math.Floor(s * 10000.0) / 10000.0;
            return s;
        }

        public static double SpatialDistance(double x1, double y1, double x2, double y2)
        {
            return Math.Sqrt(Math.Pow((y2 - y1), 2) + Math.Pow((x2 - x1), 2));
        }

        public static double SpatialDistanceGeo(double lon1, double lat1, double lon2, double lat2)
        {
            lon1 = lon1 / 180.0 * Math.PI;
            lat1 = lat1 / 180.0 * Math.PI;
            lon2 = lon2 / 180.0 * Math.PI;
            lat2 = lat2 / 180.0 * Math.PI;

            double a = lat1 - lat2;
            double b = lon1 - lon2;
            double s = 2 * Math.Asin(Math.Sqrt(Math.Pow(Math.Sin(a / 2), 2) + Math.Cos(lat1) * Math.Cos(lat2) * Math.Pow(Math.Sin(b / 2), 2)));

            s = s * 6378137;
            s = Math.Floor(s * 10000.0) / 10000.0;
            return s;
        }

        public static double DirectionAngle(IPoint p1, IPoint p2)
        {
            double p1X = p1.GetX();
            double p1Y = p1.GetY();
            double p2X = p2.GetX();
            double p2Y = p2.GetY();

            if (p1X == p2X && p1Y == p2Y) return -1;
            double angle;
            if (p1X < p2X)
            {
                if (p1Y == p2Y) return 0;
                double tanValue = (p2X - p1X) / (p2Y - p1Y);
                angle = Math.Atan(tanValue) * 180.0 / Math.PI;
            }
            else
            {
                if (p1Y == p2Y) return 180;
                double tanValue = (p2X - p1X) / (p2Y - p1Y);
                angle = Math.Atan(tanValue) * 180.0 / Math.PI + 180.0;
            }

            return angle;
        }

        public static double DirectionAngleGeo(IPoint p1, IPoint p2)
        {
            double p1X = p1.GetX();
            double p1Y = p1.GetY();
            double p2X = p2.GetX();
            double p2Y = p2.GetY();

            if (p1X == p2X && p1Y == p2Y) return -1;

            double angle;
            if (p1X < p2X)
            {
                if (p1Y == p2Y) return 90;
                double distanceY = SpatialDistanceGeo(p1X, p1Y, p1X, p2Y);
                double distanceX = SpatialDistanceGeo(p1X, p2Y, p2X, p2Y);
                if (p1Y > p2Y) distanceY = -distanceY;
                double tanValue = distanceX / distanceY;
                angle = Math.Atan(tanValue) * 180.0 / Math.PI;

                if (angle == 0 && p1Y > p2Y)
                {
                    angle = 180.0;
                }

                if (angle < 0) angle = 180.0 + angle;
            }
            else
            {
                if (p1Y == p2Y) return 270;
                double distanceY = SpatialDistanceGeo(p1X, p1Y, p1X, p2Y);
                double distanceX = -SpatialDistanceGeo(p1X, p2Y, p2X, p2Y);
                if (p1Y > p2Y) distanceY = -distanceY;
                double tanValue = distanceX / distanceY;
                angle = Math.Atan(tanValue) * 180.0 / Math.PI;

                if (angle == 0)
                {
                    if (p1Y > p2Y)
                    {
                        angle = 180.0;
                    }
                    else
                    {
                        angle = 0.0;
                    }
                }
                else if (angle > 0)
                {
                    angle += 180.0;
                }
                else
                {
                    angle = 360.0 + angle;
                }
            }

            return angle;
        }

        public static double SpatialDistance(double lineStartX, double lineStartY, double lineEndX, double lineEndY, double pointX, double pointY)
        {
            // 一条竖线k=∞
            if (lineStartX == lineEndX)
            {
                if ((lineEndY <= pointY && lineStartY >= pointY) || (lineEndY >= pointY && lineStartY <= pointY))
                {
                    return Math.Abs(pointX - lineStartX);
                }
                else
                {
                    double toStartDistance = DistanceCalculator.SpatialDistance(lineStartX, lineStartY, pointX, pointY);
                    double toEndDistance = DistanceCalculator.SpatialDistance(lineEndX, lineEndY, pointX, pointY);
                    return toStartDistance > toEndDistance ? toEndDistance : toStartDistance;
                }
            }
            // 一条横线
            else if (lineStartY == lineEndY)
            {
                if ((lineEndX <= pointX && lineStartX >= pointX) || (lineEndX >= pointX && lineStartX <= pointX))
                {
                    return Math.Abs(pointY - lineStartY);
                }
                else
                {
                    double toStartDistance = DistanceCalculator.SpatialDistance(lineStartX, lineStartY, pointX, pointY);
                    double toEndDistance = DistanceCalculator.SpatialDistance(lineEndX, lineEndY, pointX, pointY);
                    return toStartDistance > toEndDistance ? toEndDistance : toStartDistance;
                }
            }
            else
            {
                double k = (lineEndY - lineStartY) / (lineEndX - lineStartX);

                // 垂线交点X,Y
                double crossX = (k * pointY - k * lineStartY + pointX + lineStartX * k * k) / (k * k + 1);
                double crossY = k * (crossX - lineStartX) + lineStartY;

                if ((lineEndX <= crossX && lineStartX >= crossX) || (lineEndX >= crossX && lineStartX <= crossX))
                {
                    return DistanceCalculator.SpatialDistance(crossX, crossY, pointX, pointY);
                }
                else
                {
                    double toStartDistance = DistanceCalculator.SpatialDistance(lineStartX, lineStartY, pointX, pointY);
                    double toEndDistance = DistanceCalculator.SpatialDistance(lineEndX, lineEndY, pointX, pointY);
                    return toStartDistance > toEndDistance ? toEndDistance : toStartDistance;
                }
            }
        }

        public static double SpatialDistanceGeo(double lineStartX, double lineStartY, double lineEndX, double lineEndY, double pointX, double pointY)
        {
            // 一条竖线k=∞
            if (lineStartX == lineEndX)
            {
                if ((lineEndY <= pointY && lineStartY >= pointY) || (lineEndY >= pointY && lineStartY <= pointY))
                {
                    return DistanceCalculator.SpatialDistanceGeo(lineStartX, pointY, pointX, pointY);
                }
                else
                {
                    double toStartDistance = DistanceCalculator.SpatialDistanceGeo(lineStartX, lineStartY, pointX, pointY);
                    double toEndDistance = DistanceCalculator.SpatialDistanceGeo(lineEndX, lineEndY, pointX, pointY);
                    return toStartDistance > toEndDistance ? toEndDistance : toStartDistance;
                }
            }
            // 一条横线
            else if (lineStartY == lineEndY)
            {
                if ((lineEndX <= pointX && lineStartX >= pointX) || (lineEndX >= pointX && lineStartX <= pointX))
                {
                    return DistanceCalculator.SpatialDistanceGeo(pointX, lineStartY, pointX, pointY);
                }
                else
                {
                    double toStartDistance = DistanceCalculator.SpatialDistanceGeo(lineStartX, lineStartY, pointX, pointY);
                    double toEndDistance = DistanceCalculator.SpatialDistanceGeo(lineEndX, lineEndY, pointX, pointY);
                    return toStartDistance > toEndDistance ? toEndDistance : toStartDistance;
                }
            }
            else
            {
                double k = (lineEndY - lineStartY) / (lineEndX - lineStartX);

                // 垂线交点X,Y
                double crossX = (k * pointY - k * lineStartY + pointX + lineStartX * k * k) / (k * k + 1);
                double crossY = k * (crossX - lineStartX) + lineStartY;

                if ((lineEndX <= crossX && lineStartX >= crossX) || (lineEndX >= crossX && lineStartX <= crossX))
                {
                    return DistanceCalculator.SpatialDistanceGeo(crossX, crossY, pointX, pointY);
                }
                else
                {
                    double toStartDistance = DistanceCalculator.SpatialDistanceGeo(lineStartX, lineStartY, pointX, pointY);
                    double toEndDistance = DistanceCalculator.SpatialDistanceGeo(lineEndX, lineEndY, pointX, pointY);
                    return toStartDistance > toEndDistance ? toEndDistance : toStartDistance;
                }
            }
        }

        // 直角坐标系下的点到线段距离
        public static double SpatialDistance(ISingleLine singleLine, IPoint point)
        {
            return DistanceCalculator.SpatialDistance(singleLine.GetStartPoint().GetX(), singleLine.GetStartPoint().GetY(), singleLine.GetEndPoint().GetX(), singleLine.GetEndPoint().GetY(), point.GetX(), point.GetY());
        }

        // 地理坐标系下的点到线段的距离
        public static double SpatialDistanceGeo(ISingleLine singleLine, IPoint point)
        {
            return DistanceCalculator.SpatialDistanceGeo(singleLine.GetStartPoint().GetX(), singleLine.GetStartPoint().GetY(), singleLine.GetEndPoint().GetX(), singleLine.GetEndPoint().GetY(), point.GetX(), point.GetY());
        }

        public static Point CrossPoint(double lineStartX, double lineStartY, double lineEndX, double lineEndY, double pointX, double pointY)
        {
            // 一条竖线k=∞
            if (lineStartX == lineEndX)
            {
                if ((lineEndY <= pointY && lineStartY >= pointY) || (lineEndY >= pointY && lineStartY <= pointY))
                {
                    return new Point(lineStartX, pointY);
                }
                else
                {
                    double toStartDistance = DistanceCalculator.SpatialDistance(lineStartX, lineStartY, pointX, pointY);
                    double toEndDistance = DistanceCalculator.SpatialDistance(lineEndX, lineEndY, pointX, pointY);
                    return toStartDistance > toEndDistance ? new Point(lineEndX, lineEndY) : new Point(lineStartX, lineStartY);
                }
            }
            // 一条横线
            else if (lineStartY == lineEndY)
            {
                if ((lineEndX <= pointX && lineStartX >= pointX) || (lineEndX >= pointX && lineStartX <= pointX))
                {
                    return new Point(pointX, lineStartY);
                }
                else
                {
                    double toStartDistance = DistanceCalculator.SpatialDistance(lineStartX, lineStartY, pointX, pointY);
                    double toEndDistance = DistanceCalculator.SpatialDistance(lineEndX, lineEndY, pointX, pointY);
                    return toStartDistance > toEndDistance ? new Point(lineEndX, lineEndY) : new Point(lineStartX, lineStartY);
                }
            }
            else
            {
                double k = (lineEndY - lineStartY) / (lineEndX - lineStartX);

                // 垂线交点X,Y
                double crossX = (k * pointY - k * lineStartY + pointX + lineStartX * k * k) / (k * k + 1);
                double crossY = k * (crossX - lineStartX) + lineStartY;

                if ((lineEndX <= crossX && lineStartX >= crossX) || (lineEndX >= crossX && lineStartX <= crossX))
                {
                    return new Point(crossX, crossY);
                }
                else
                {
                    double toStartDistance = DistanceCalculator.SpatialDistance(lineStartX, lineStartY, pointX, pointY);
                    double toEndDistance = DistanceCalculator.SpatialDistance(lineEndX, lineEndY, pointX, pointY);
                    return toStartDistance > toEndDistance ? new Point(lineEndX, lineEndY) : new Point(lineStartX, lineStartY);
                }
            }
        }

        public static Point CrossPoint(ISingleLine singleLine, IPoint point)
        {
            return DistanceCalculator.CrossPoint(singleLine.GetStartPoint().GetX(), singleLine.GetStartPoint().GetY(), singleLine.GetEndPoint().GetX(), singleLine.GetEndPoint().GetY(), point.GetX(), point.GetY());
        }

        public static Point CrossPoint2(double lineStartX, double lineStartY, double lineEndX, double lineEndY, double pointX, double pointY)
        {
            // 一条竖线k=∞
            if (lineStartX == lineEndX)
            {
                if ((lineEndY <= pointY && lineStartY >= pointY) || (lineEndY >= pointY && lineStartY <= pointY))
                {
                    return new Point(lineStartX, pointY);
                }
            }
            // 一条横线
            else if (lineStartY == lineEndY)
            {
                if ((lineEndX <= pointX && lineStartX >= pointX) || (lineEndX >= pointX && lineStartX <= pointX))
                {
                    return new Point(pointX, lineStartY);
                }
            }
            else
            {
                double k = (lineEndY - lineStartY) / (lineEndX - lineStartX);

                // 垂线交点X,Y
                double crossX = (k * pointY - k * lineStartY + pointX + lineStartX * k * k) / (k * k + 1);
                double crossY = k * (crossX - lineStartX) + lineStartY;

                if ((lineEndX <= crossX && lineStartX >= crossX) || (lineEndX >= crossX && lineStartX <= crossX))
                {
                    return new Point(crossX, crossY);
                }
            }

            return null;
        }

        public static Point CrossPoint2(ISingleLine singleLine, IPoint point)
        {
            return DistanceCalculator.CrossPoint2(singleLine.GetStartPoint().GetX(), singleLine.GetStartPoint().GetY(), singleLine.GetEndPoint().GetX(), singleLine.GetEndPoint().GetY(), point.GetX(), point.GetY());
        }
    }
}
